var Utils = require('geoUtils');

cc.Class({
    extends: cc.Component,

    properties: {
        traceContainer: {
            default: null,
            type: cc.Node,
            tooltip: '该节点下所有节点从上到下依次作为轨迹点(关键点)，最终与trace中预设的轨迹点合并且后置于trace属性中的轨迹点'
        },
        trace: {
            default: [],
            type: [cc.Node],
            tooltip: '作为轨迹的有序节点(关键点)',
        },
        interpolation: {
            default: 0,
            type: cc.Float,
            tooltip: '若大于0，则在关键节点间每隔interpolation，插入一个补间节点'
        },
        progressEvents: {
            default: [],
            type: [cc.Component.EventHandler],
            tooltip: '滑动过程事件',
        },
        completeEvents: {
            default: [],
            type: [cc.Component.EventHandler],
            tooltip: '划动过程完毕事件'
        },
        stopEvents: {
            default: [],
            type: [cc.Component.EventHandler],
            tooltip: '划动操作停止时调用',
        },
        wrongTraceEvents: {
            default: [],
            type: [cc.Component.EventHandler],
            tooltip: '(该方法即将被废除)划动轨迹错误时触发',
        },
        divergeEvents: {
            default: [],
            type: [cc.Component.EventHandler],
            tooltip: '划动偏离时触发事件',
        },
        divergeTolerance: {
            default: 0,
            type: cc.Float,
            tooltip: '偏离报警的容忍距离',
        },
        threshold: {
            default: -1,
            type: cc.Integer,
            tooltip: '接触判定阈值，距离（像素），负值则采用trace节点包围盒用于接触判定'
        },
        graphics: {
            default: null,
            type: cc.Graphics,
            tooltip: '可根据过程绘制轨迹'
        },
        useFreeTrace: {
            default: false,
            tooltip: '是否绘制自由轨迹，即便不在预定轨迹上，消耗大'
        },
        clearWhenRight:{
            default: false,
            tooltip: '正确连接时删除所有轨迹'
        },
        cleanWhenStop:{
            default:true,
            tooltip:'画线中断时，是否清除脏轨迹'
        }
    },
    onLoad() {
        // 收集关键点
        if (this.traceContainer != null)
            this.trace = this.trace.concat(this.traceContainer.children);
        //
        this._keyTrace = this.trace.concat();
        // 插入补充节点
        this._interpolate();
    },
    /**
     * 接收来自geoDraggable组件的拖拽事件
     * @param {*} e 
     * @param {*} draggee 被拖拽物
     * @param {*} location 拖拽点，即触摸点，世界坐标
     */
    onDrag(e, draggee, location) {
        this._traceIndexToPass = this._traceIndexToPass || 0;
        // 若果已经完毕则不再处理
        if (this._traceIndexToPass === this.trace.length)
            return;
        //
        var needCollectPosition = true;

        // 进行偏航判定
        var isDiverge = this._checkDiverge(this._traceIndexToPass, location);

        var currentPassedNodeIndex = this._getCurrentPassedNodeIndex(this._traceIndexToPass, location);
        //
        var rightOrder = true;
        if (currentPassedNodeIndex == this._traceIndexToPass) {
            // 获得了正确顺序的节点
            this._emitEvent();
            needCollectPosition = false;
        } else if (currentPassedNodeIndex >= 0) {
            // 划过了错误顺序的节点
            this._emitWrongEvent();
            //
            rightOrder = false;
        }
        //
        if (this.getProgress() > 0) {
            if (needCollectPosition === true && this.useFreeTrace === true)
                this._collectPosition(location);
            this._drawTrace(rightOrder);
        }
    },
    onDragEnd(e, draggee, location) {
        var nodesPassed = this.trace.slice(0, this._traceIndexToPass);
        var progress = this.getProgress();
        // 准备事件
        var event = new cc.Event.EventCustom('trace');
        event.target = this;
        event.nodesPassed = nodesPassed;
        event.nodes = this.trace;
        event.nextIndexToPass = this._traceIndexToPass;
        event.progress = progress;
        // 过程事件
        event.name = 'trace-stop';
        cc.Component.EventHandler.emitEvents(this.stopEvents, event, nodesPassed, progress);
        // 清除脏轨迹
        if (this.cleanWhenStop === true){
            this._prepareTrace();
            this._drawTrace(true);
        }
    },
    resetProgress() {
        // 准备执行通过判定的节点的索引值
        this._traceIndexToPass = 0;
        //
        this._drawTrace(true);
    },
    getProgress() {
        return this._traceIndexToPass / this.trace.length;
    },
    _emitEvent() {
        //
        this._traceIndexToPass++;
        // 整理要发送的节点
        var nodesPassed = this.trace.slice(0, this._traceIndexToPass);
        var progress = this.getProgress();
        // 准备事件
        var event = new cc.Event.EventCustom('trace');
        event.target = this;
        event.nodesPassed = nodesPassed;
        event.nodes = this.trace;
        event.nextIndexToPass = this._traceIndexToPass;
        event.progress = progress;
        // 结束判定
        if (this._traceIndexToPass !== this.trace.length) {
            // 过程事件
            event.name = 'trace-progress';
            cc.Component.EventHandler.emitEvents(this.progressEvents, event, nodesPassed, progress)
        } else {
            // 结束事件
            event.name = 'trace-complete';
            cc.Component.EventHandler.emitEvents(this.completeEvents, event, nodesPassed, progress)
        }
        //
        this._prepareTrace();
    },
    _emitWrongEvent(wrongPassedNodeIndex) {
        //
        // this._traceIndexToPass++;
        // 整理要发送的节点
        var nodesPassed = this.trace.slice(0, this._traceIndexToPass);
        var progress = this.getProgress();
        // 准备事件
        var event = new cc.Event.EventCustom('trace');
        event.target = this;
        event.nodesPassed = nodesPassed;
        event.nodes = this.trace;
        event.nextIndexToPass = this._traceIndexToPass;
        event.progress = progress;
        event.wrongPassedNodeIndex = wrongPassedNodeIndex;
        // 过程事件
        event.name = 'trace-wrong';
        cc.Component.EventHandler.emitEvents(this.wrongTraceEvents, event, nodesPassed, progress, wrongPassedNodeIndex);
        // 错误情况不清除线
        // this._prepareTrace();
    },
    /**
     * 清理脏轨迹
     */
    _prepareTrace() {
        //
        if (this.graphics == null)
            return;
        //
        this._clearPositions();
        //
        if (this._traceIndexToPass < 2)
            return;
        //
        if (this.clearWhenRight !== true) {
            for (var i = 0; i < this._traceIndexToPass; ++i) {
                var n = this.trace[i];
                var p = Utils.node.getNodeToWorldPosition(n.parent, n.position);
                this._collectPosition(p);
            }
        }
    },
    _drawTrace(bClear) {
        if (this.graphics == null)
            return;
        //
        if (bClear == true) {
            this.graphics.clear();
            this._drawPositionIndex = 0;
        }
        this._drawPositionIndex = (this._drawPositionIndex === void 0) ? 0 : this._drawPositionIndex;
        //
        if (this._positions.length > 1 && this._drawPositionIndex < this._positions.length) {
            var p = this._positions[this._drawPositionIndex];
            this.graphics.moveTo(p.x, p.y);
            for (var i = this._drawPositionIndex + 1; i < this._positions.length; ++i) {
                p = this._positions[i];
                this.graphics.lineTo(p.x, p.y);
                //
                this._drawPositionIndex = i;
            }
            this.graphics.stroke();
        }
        // if (this._positions.length > 1) {
        //     var p = this._positions[0];
        //     this.graphics.moveTo(p.x, p.y);
        //     for (var i = 0; i < this._positions.length; ++i) {
        //         p = this._positions[i];
        //         this.graphics.lineTo(p.x, p.y);
        //     }
        //     this.graphics.stroke();
        // }
    },
    _clearPositions() {
        this._positions = [];
    },
    _collectPosition(positionInWorld) {
        if (this.graphics == null)
            return;
        this._positions = this._positions || [];
        // 转换坐标
        var positionInNode = Utils.node.getWorldToNodePosition(this.graphics.node, positionInWorld);
        // 记录最新的划动位置
        this._positions.push(positionInNode);
    },
    /**
     * 从startIndex开始，检测当前划过的节点的索引
     * @param {int} startIndex 
     * @return {int} -1，没有划过任何节点
     */
    _getCurrentPassedNodeIndex(startIndex, location) {
        var retIndex = -1;
        for (var i = startIndex; i < this.trace.length; ++i) {
            // 获取节点
            var traceNode = this.trace[i];
            // 判定通过
            // var needCollectPosition = true;
            if (this.threshold > 0) {
                // 获取节点世界坐标
                var nodePos = Utils.node.getWorldPosition(traceNode);
                // 计算距离
                var dist = nodePos.sub(location).mag();
                //
                if (dist <= this.threshold) {
                    // this._emitEvent();
                    // needCollectPosition = false;
                    retIndex = i;
                    break;
                }
            } else {
                // 获取节点包围和
                var nodeBox = traceNode.getBoundingBoxToWorld();
                if (nodeBox.contains(location) === true) {
                    // this._emitEvent();
                    // needCollectPosition = false;
                    retIndex = i;
                    break;
                }
            }
        }
        //
        return retIndex;
    },
    /**
     * 检测是否偏航
     * @param {*} nodeToCheckIndex 
     * @param {*} location 
     * @returns {boolean} true，则偏航；false，则无恙
     */
    _checkDiverge(nodeToCheckIndex, location) {
        if (this._lastNodeToCheckIndex != nodeToCheckIndex) {
            // 若是新节点，则重置距离参考
            this._lastDistanceToCheckNode = Number.POSITIVE_INFINITY;
            this._lastNodeToCheckIndex = nodeToCheckIndex;
        }
        // 计算本次距离
        var nodeToCheck = this.trace[nodeToCheckIndex];
        var nodePosInWorld = Utils.node.getWorldPosition(nodeToCheck);
        var dist = nodePosInWorld.sub(location).mag();
        //
        var isDiverge = dist - this._lastDistanceToCheckNode
        // 记录本次距离
        this._lastDistanceToCheckNode = dist;
        // 若正在偏离且大于容忍值，则发送事件
        if (dist > this.divergeTolerance && isDiverge > 0) {
            var nodesPassed = this.trace.slice(0, this._traceIndexToPass);
            var progress = this.getProgress();
            // 准备事件
            var event = new cc.Event.EventCustom('trace');
            event.target = this;
            event.nodesPassed = nodesPassed;
            event.nodes = this.trace;
            event.nextIndexToPass = this._traceIndexToPass;
            event.progress = progress;
            // 过程事件
            event.name = 'trace-diverge';
            cc.Component.EventHandler.emitEvents(this.divergeEvents, event, nodesPassed, progress, nodeToCheck);
            //
            return true;
        }
        //
        return false;
    },
    /**
     * 填充补间节点
     */
    _interpolate() {
        if (this.interpolation <= 0)
            return;
        //
        var n0 = this.trace[0];
        var n1;
        // 先把第一个节点放入临时数组
        // 设置父节点
        var traceParent = this.traceContainer;
        if (traceParent == null)
            traceParent = this.node;
        //
        var tmpTrace = [];
        tmpTrace.push(n0);
        //
        for (var i = 1; i < this.trace.length; ++i) {
            n1 = this.trace[i];
            let p0 = Utils.node.getWorldPosition(n0);
            let p1 = Utils.node.getWorldPosition(n1);
            // 计算需要的分段数
            let d = p0.sub(p1).mag();
            let n = Math.floor(d / this.interpolation);
            if (n > 1) {
                var step = 1.0 / n;
                for (var j = 1; j < n; ++j) {
                    // 获取位置
                    let p = p0.lerp(p1, step * j);
                    // 创建新节点
                    var newNode = cc.instantiate(n0);
                    newNode.parent = traceParent;
                    //
                    Utils.node.setWorldPosition(newNode, p);
                    // 记录新节点
                    tmpTrace.push(newNode);
                }
            }
            //
            tmpTrace.push(n1);
            n0 = n1;
        }
        //
        this.trace = tmpTrace;
    }
});
